package com.smartStatus.security.comment;

import cn.hutool.core.util.StrUtil;
import com.smartStatus.core.constant.CommonConstants;
import com.smartStatus.core.constant.SecurityConstant;
import com.smartStatus.core.constant.StatusUser;
import com.smartStatus.core.constant.SysUser;
import lombok.extern.slf4j.Slf4j;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import sun.security.util.SecurityConstants;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * @ClassName : StatusUserAuthenticationConverter
 * @Author : lby
 * @Date: 2021/5/6 21:22
 * @Description :
 */
@Slf4j
public class StatusUserAuthenticationConverter implements UserAuthenticationConverter {
    private static final String N_A = "N/A";

    /**
     * Extract information about the user to be used in an access token (i.e. for resource servers).
     *
     * @param authentication an authentication representing a user
     * @return a map of key values representing the unique information about the user
     */
    @Override
    public Map<String, ?> convertUserAuthentication(Authentication authentication) {
        Map<String, Object> response = new LinkedHashMap<>();
        response.put(USERNAME, authentication.getName());
        if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
            response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
        }
        return response;
    }

    /**
     * Inverse of {@link #convertUserAuthentication(Authentication)}. Extracts an Authentication from a map.
     *
     * @param map a map of user information
     * @return an Authentication representing the user or null if there is none
     */
    @Override
    public Authentication extractAuthentication(Map<String, ?> map) {
        if (map.containsKey(USERNAME)) {
            validateTenantId(map);
            Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
            String username = (String) map.get(USERNAME);
            Integer id = (Integer) map.get(SecurityConstant.DETAILS_USER_ID);
            Integer deptId = (Integer) map.get(SecurityConstant.DETAILS_DEPT_ID);
            Map<String, Object> newMap = (Map<String, Object>) map.get("sysUser");
            Integer newId = (Integer) newMap.get("tenantId");
            Integer tenantId = newId;
            ObjectMapper mapper = new ObjectMapper();
            LinkedHashMap linkedHashMap = (LinkedHashMap) map.get(SecurityConstant.SYS_USER);
            linkedHashMap.remove("createTime");
            linkedHashMap.remove("updateTime");
            linkedHashMap.remove("tenantList");
            boolean enabled = StrUtil.equals(linkedHashMap.get("lockFlag").toString(), CommonConstants.STATUS_NORMAL);

            SysUser sysUser = mapper.convertValue(linkedHashMap, new TypeReference<SysUser>() {
            });
            StatusUser user = new StatusUser(id, deptId, tenantId, username, N_A, enabled
                    , true, true, true, authorities, sysUser);
            return new UsernamePasswordAuthenticationToken(user, N_A, authorities);
        }
        return null;
    }

    private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
        Object authorities = map.get(AUTHORITIES);
        if (authorities instanceof String) {
            return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);
        }
        if (authorities instanceof Collection) {
            return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils
                    .collectionToCommaDelimitedString((Collection<?>) authorities));
        }
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        return grantedAuthorities;
        //throw new IllegalArgumentException("Authorities must be either a String or a Collection");
    }

    private void validateTenantId(Map<String, ?> map) {
        String headerValue = getCurrentTenantId();
        LinkedHashMap linkedHashMap = (LinkedHashMap) map.get(SecurityConstant.SYS_USER);
        List<Integer> tenantIdList = new ArrayList<>();
        if (linkedHashMap.get("tenantIds") != null) {
            tenantIdList = (List<Integer>) linkedHashMap.get("tenantIds");
        }
        if (!StringUtils.isEmpty(headerValue) && !"0".equals(headerValue)) {
            if (StrUtil.isNotBlank(headerValue) && !tenantIdList.contains(Integer.parseInt(headerValue))) {
                log.warn("请求头中的`租户ID({})和用户的租户ID({})不一致", headerValue, tenantIdList);
                // TODO: 不要提示租户ID不对，可能被穷举
//                throw new smartStatusAuth2Exception(SpringSecurityMessageSource.getAccessor().getMessage("AbstractUserDetailsAuthenticationProvider.badTenantId","Bad tenant ID"));
            }
        }
    }

    private Optional<HttpServletRequest> getCurrentHttpRequest() {
        return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
                .filter(requestAttributes -> ServletRequestAttributes.class.isAssignableFrom(requestAttributes.getClass()))
                .map(requestAttributes -> ((ServletRequestAttributes) requestAttributes))
                .map(ServletRequestAttributes::getRequest);
    }

    private String getCurrentTenantId() {
        return getCurrentHttpRequest().map(httpServletRequest -> httpServletRequest.getHeader(CommonConstants.TENANT_ID)).orElse(null);
    }
}
